Мы работаем в стартапе, который продаёт продукты питания. Нужно разобраться, как ведут себя пользователи нашего мобильного приложения.
Предстоит изучить воронку продаж. Узнать, как пользователи доходят до покупки. Сколько пользователей доходит до покупки, а сколько — «застревает» на предыдущих шагах? На каких именно?
После этого исследуем результаты A/A/B-эксперимента. Дизайнеры захотели поменять шрифты во всём приложении, а менеджеры испугались, что пользователям будет непривычно. Договорились принять решение по результатам A/A/B-теста. Пользователей разбили на 3 группы: 2 контрольные со старыми шрифтами и одну экспериментальную — с новыми. Выясним, какой шрифт лучше.
Описание предоставленных нам для исследования данных:
Каждая запись в логе — это действие пользователя, или событие.
EventName — название события;
DeviceIDHash — уникальный идентификатор пользователя;
EventTimestamp — время события;
ExpId — номер эксперимента: 246 и 247 — контрольные группы, а 248 — экспериментальная.
Наше исследование мы разобьем на 5 шагов, в ходе которых, изучим и проверим данные, сделае выводы, по полученным результатам.
ШАГ 1
Откроем файл и изучим общую информацию.
import pandas as pd
import seaborn as sns
import scipy.stats as stats
from scipy import stats as st
import numpy as np
import datetime as dt
from matplotlib import pyplot as plt
import math as mth
data = pd.read_csv('/datasets/logs_exp.csv', sep='\t')
data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 244126 entries, 0 to 244125 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 EventName 244126 non-null object 1 DeviceIDHash 244126 non-null int64 2 EventTimestamp 244126 non-null int64 3 ExpId 244126 non-null int64 dtypes: int64(3), object(1) memory usage: 7.5+ MB
Нам предоставлен файл, содержащий 4 столбца и 244126 строк. Данные без пропусков.
ШАГ 2 ПОДГОТОВКА ДАННЫХ
# переименуем столбцы
data.columns = ['event_name', 'device_id_hash', 'event_time', 'exp_id']
# приведем в соответствие формат столбца с временем событий
data['event_time'] = pd.to_datetime(data['event_time'], unit='s')
data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 244126 entries, 0 to 244125 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 event_name 244126 non-null object 1 device_id_hash 244126 non-null int64 2 event_time 244126 non-null datetime64[ns] 3 exp_id 244126 non-null int64 dtypes: datetime64[ns](1), int64(2), object(1) memory usage: 7.5+ MB
# проверим данные на дубликаты
data.duplicated().sum()
413
413 дубликатов составляют 0.169% от всех наших данных. Не будет существенным их удаление из дальнейшего нашего исследования.
# удаляем дубликаты
data = data.drop_duplicates()
# добавим столбец с датой из времени события
data['date'] = data['event_time'].dt.date
data['date'] = pd.to_datetime(data['date'])
Отлично
С подготовкой все ОК, но рекомендую завернуть базовые проверки в функцию и использовать ее и в следующих проектах, и в целом при дальнейшей работы с данными
ШАГ 3 ИЗУЧИМ И ПРОВЕРИМ ДАННЫЕ
Сколько всего событий в логе?
Сколько всего пользователей в логе?
Сколько в среднем событий приходится на пользователя?
Данными за какой период мы располагаете? Найдем максимальную и минимальную дату. Построим гистограмму по дате и времени.
Определим, с какого момента данные полные и отбросим более старые.
Проверим много ли событий и пользователей мы потеряли, отбросив старые данные.
Проверим, что есть пользователи из всех трёх экспериментальных групп.
# сколько всего событий в логе
print("Количество видов событий в логе:", len(data['event_name'].unique()))
print("Наименование событий в логе:", data['event_name'].unique())
print("Количество событий в логе:", data['event_name'].count())
all_event = data['event_name'].count()
Количество видов событий в логе: 5 Наименование событий в логе: ['MainScreenAppear' 'PaymentScreenSuccessful' 'CartScreenAppear' 'OffersScreenAppear' 'Tutorial'] Количество событий в логе: 243713
# сколько всего пользователей в логе
print("Количество пользователей:", len(data['device_id_hash'].unique()))
all_user = len(data['device_id_hash'].unique())
Количество пользователей: 7551
# сколько в среднем событий приходится на пользователя
print("Количество событий на одного пользователя:", (all_event/all_user).round())
Количество событий на одного пользователя: 32.0
# определим максимальную и минимальную дату событий
print("Минимальная дата событий:", data['date'].min())
print("Максимальная дата событий:", data['date'].max())
Минимальная дата событий: 2019-07-25 00:00:00 Максимальная дата событий: 2019-08-07 00:00:00
# построим гистограмму по дате и времени
data['event_time'].hist(bins=14*24, figsize=(10, 5))
plt.title('Количество событий по дате')
plt.xlabel('Дата и время')
plt.ylabel('Количество событий')
plt.show()
Рекомендация
При построении гистограмм, отражающих временные отрезки, количество бинов стоит определять так, чтобы каждый бин точно соответствовал определенному временному промежутку
Например, так bins=14*24
Гисторамма нам показала, что после 08 августа у нас нет событий вообще, как мы и увидили ранее, а вот до 01 августа их крайне мало. Уточним количество событий до 01.08 и при подтвеждении их низначительности удалим из дальнейшего исследования.
# удалим данны до 01.08 и посчитаем новое количество событий и пользователей:
data_new = data[data['date'] >= '2019-08-01']
print("Количество событий в логе после 01.08:", data_new['event_name'].count())
print("Количество пользователей после 01.08:", len(data_new['device_id_hash'].unique()))
Количество событий в логе после 01.08: 240887 Количество пользователей после 01.08: 7534
Как видим, мы удалили 2826 событий и 17 пользователей. Это приблизительно 1% событий и 0% (0,225%) пользователей.
Совершенно правомерно можем оставить в исследовании новый период с 01.08.2019 по 07.08.2019
print("Количество удаленных событий в логе :", data['event_name'].count()-data_new['event_name'].count())
print('Процентное соотношение удаленных событий:',100-100*data_new['event_name'].count()/data['event_name'].count())
print("Количество удаленных пользователей :", len(data['device_id_hash'].unique())-len(data_new['device_id_hash'].unique()))
print('Процентное соотношение удаленных пользоватлей:', 100-100*len(data_new['device_id_hash'].unique())/len(data['device_id_hash'].unique()))
Количество удаленных событий в логе : 2826 Процентное соотношение удаленных событий: 1.1595606307418933 Количество удаленных пользователей : 17 Процентное соотношение удаленных пользоватлей: 0.22513574361012445
# проверим наличие пользователей из всех трёх экспериментальных групп
print(data_new.groupby('exp_id')['device_id_hash'].nunique())
exp_id 246 2484 247 2513 248 2537 Name: device_id_hash, dtype: int64
👍🏻
Все три группы пользователей эксперимента у нас присутствуют. При этом контрольные группы 246 и 247 отличаются друг от друга на 29 пользователей, что составляет 1% , а экспериментальная 248 отличается от 246 на 53 пользователя (2%), а от 247 группы на 24 пользователя (0,955%).
Таким образом, мы убедили, что полученные и изученные данные у нас корректные и можно продолжать исследование.
ШАГ 4 ИЗУЧИМ ВОРОНКУ СОБЫТИЙ
Посмотрим, какие события есть в логах, как часто они встречаются. Отсортируем события по частоте.
Посчитаем, сколько пользователей совершали каждое из этих событий. Отсортируем события по числу пользователей. Посчитаем долю пользователей, которые хоть раз совершали событие.
Предположим, в каком порядке происходят события.
По воронке событий посчитаем, какая доля пользователей проходит на следующий шаг воронки (от числа пользователей на предыдущем). То есть для последовательности событий A → B → C посчитайте отношение числа пользователей с событием B к количеству пользователей с событием A, а также отношение числа пользователей с событием C к количеству пользователей с событием B.
Проанализируем на каком шаге теряете больше всего пользователей.
Посмотрим, какая доля пользователей доходит от первого события до оплаты.
# какие события есть в логах, как часто они встречаются
data_new['event_name'].value_counts()
MainScreenAppear 117328 OffersScreenAppear 46333 CartScreenAppear 42303 PaymentScreenSuccessful 33918 Tutorial 1005 Name: event_name, dtype: int64
Получили 5 видов событий: "главный экран" - 117328 событий, "экран с предложениями" - 46333 событий, "экран с корзиной" - 42303 событий, экран "оплата прошла успешно" - 33918 событий и экран "руководство" (что бы это не значило, руководство пользователя или консультация по товару) - 1005 событий.
# посчитаем, в том числе и в долях, сколько пользователей совершили каждое из этих событий
print(data_new.groupby('event_name')['device_id_hash'].nunique().sort_values(ascending=False))
print(data_new.groupby('event_name')['device_id_hash'].nunique().sort_values(ascending=False) / data_new['device_id_hash'].nunique())
# нигде в задании не указано, что расчет нужно показывать в процентном соотношнии, везде говориться о "доле"
# а "заказчик" всегда прав
# из этого соображения, в работе не найти перевод долей в проценты (обнимаю, если позволительно)
event_name MainScreenAppear 7419 OffersScreenAppear 4593 CartScreenAppear 3734 PaymentScreenSuccessful 3539 Tutorial 840 Name: device_id_hash, dtype: int64 event_name MainScreenAppear 0.984736 OffersScreenAppear 0.609636 CartScreenAppear 0.495620 PaymentScreenSuccessful 0.469737 Tutorial 0.111495 Name: device_id_hash, dtype: float64
В долевом отношении события выстроились в следующем порядке:
7419 пользователей, 0.98, перешли на главный экран
4593 пользователя, 0.61, попали на экран с предложениями с продуктами питания
3734 пользователя, 0.50, отметились на экране с корзиной товаров
3539 пользователей, 0.47, увидили экран с ответом об успешно поведенной оплате
840 пользователей, 0.11, посетили сраницу с руководством (обучением, консультацией).
Из данных мы видим, что последний этап, с наименьшей долей пользователей, можно не включать в дальнейшее исследование и порядок действий пользователей выглядит следующим образом: перешли на главный экран --> перешли на преложения товаров --> перешли в корзину --> успешно оплатили покупки.
Верные расчеты и рассуждения 👍
Справка, скорее, опциональный момент, который может происходит на любом из этапов и не вписывается в платежную воронку
И не стоит выводить на экран датафреймы с помощью принта. Лучше использовать display, а последнюю введенную переменную Юпитер итак выведет в читаемом виде
# составим таблицу из полученных данных о количестве логов, пользователей этих логов и долевом их отношении,
# без этапа Tutorial
data_new2 = data_new.groupby('event_name')['device_id_hash'].nunique().sort_values(ascending=False).reset_index()
data_new2['part'] = (data_new2['device_id_hash'] / data_new['device_id_hash'].nunique())
data_new2 = data_new2.drop(index=4)
data_new2
| event_name | device_id_hash | part | |
|---|---|---|---|
| 0 | MainScreenAppear | 7419 | 0.984736 |
| 1 | OffersScreenAppear | 4593 | 0.609636 |
| 2 | CartScreenAppear | 3734 | 0.495620 |
| 3 | PaymentScreenSuccessful | 3539 | 0.469737 |
# по воронке событий посчитаем, какая доля пользователей проходит на следующий шаг воронки
#print("Доля пользователей, которая доходит от первого события до предложений:", data_new2.loc[1, 'device_id_hash'] / (data_new2.loc[0, 'device_id_hash']))
#print("Доля пользователей, которая доходит от предложений до корзины:", data_new2.loc[2, 'device_id_hash'] / (data_new2.loc[1, 'device_id_hash']))
#print("Доля пользователей, которая доходит от корзины до оплаты:", data_new2.loc[3, 'device_id_hash'] / (data_new2.loc[2, 'device_id_hash']))
Доля пользователей, относительно предыдущего шага посчитана верно 👍
Еще можно воспользоваться методом shift
По ссылке отличное видео про применение оконок в пандасе. Про шифт тоже есть
https://www.youtube.com/watch?v=yQ7qHZBY5xI&t=1s&ab_channel=karpov.courses
# по воронке событий посчитаем, какая доля пользователей проходит на следующий шаг воронки
index=0
for i in data_new2['device_id_hash']:
if index ==0:
data_new2.loc[index, 'funnel'] = 1
index=index+1
else:
data_new2.loc[index, 'funnel'] =round(data_new2.loc[index,'device_id_hash']/data_new2.loc[index-1,'device_id_hash'], 2)
index=index+1
display(data_new2)
| event_name | device_id_hash | part | funnel | |
|---|---|---|---|---|
| 0 | MainScreenAppear | 7419 | 0.984736 | 1.00 |
| 1 | OffersScreenAppear | 4593 | 0.609636 | 0.62 |
| 2 | CartScreenAppear | 3734 | 0.495620 | 0.81 |
| 3 | PaymentScreenSuccessful | 3539 | 0.469737 | 0.95 |
Как видим, из всего количества посетителей, только 0,62 доля переходит на страницу товаров. Это больше третьей части от всех посетелителей мы терям. Вот в следующих этапах такой большой доли потерь нет. В корзину переходят 0.81 доля посетителей со страницы товаров, а из корзины переходят к успешной оплате практически все посетители, 0.95 доля от всех, совершивших выбор товара.
print("Доля пользователей, которая доходит от первого события до оплаты:", data_new2.loc[3, 'device_id_hash'] / (data_new2.loc[0, 'device_id_hash']))
Доля пользователей, которая доходит от первого события до оплаты: 0.47701846610055265
В ходе 4 шага нашего исследования мы
получили 5 видов событий: "главный экран" - 117328 событий, "экран с предложениями" - 46333 событий, "экран с корзиной" - 42303 событий, экран "оплата прошла успешно" - 33918 событий и экран "руководство" - 1005 событий.
выяснили, что в долевом отношении события выстроились в следующем порядке:
7419 пользователей, 0.98, перешли на главный экран
4593 пользователя, 0.61, попали на экран с предложениями с продуктами питания
3734 пользователя, 0.50, отметились на экране с корзиной товаров
3539 пользователей, 0.47, увидили экран с ответом об успешно поведенной оплате
840 пользователей, 0.11, посетили сраницу с руководством (обучением, консультацией).
Из данных мы видим, что последний этап, с наименьшей долей пользователей, можно не включать в дальнейшее исследование и порядок действий пользователей выглядит следующим образом: перешли на главный экран --> перешли на преложения товаров --> перешли в корзину --> успешно оплатили покупки.
Определили, что из всего количества посетителей, только 0,619 доля переходит на страницу товаров. Это больше третьей части от всех посетелителей мы терям. Вот в следующих этапах такой большой доли потерь нет. В корзину переходят 0.81 доля посетителей со страницы товаров, а из корзины переходят к успешной оплате практически все посетители, 0.95 доля от всех, совершивших выбор товара.
В итоге только (с радостью или грустью) 0.477 часть покупателей, попавшая на главную страницу доводит дело до успешной покупки.
Рекомендация
Стоит выдвинуть предположения, что может стоять за этими числами. И гипотезы, как эти числа можно улучшить
По ссылке можно ознакомиться с различными графиками воронок
Здорово, что строишь воронку 👍
Но их, как правило, строят, выводя на график число пользователей. А доли уже можно доп. информацией вывести
import plotly.express as px
fig = px.funnel(data_new2, y='event_name', x='funnel', title='Воронка последовательности событий')
fig.show()
ШАГ 5 ИЗУЧИМ РЕЗУЛЬТАТЫ ЭКСПЕРИМЕНТА
В следующем нашем шаге исследования определим
сколько пользователей в каждой экспериментальной группе
есть 2 контрольные группы для А/А-эксперимента, чтобы проверить корректность всех механизмов и расчётов. Проверим, находят ли статистические критерии разницу между выборками 246 и 247.
выберим самое популярное событие. Посчитаем число пользователей, совершивших это событие в каждой из контрольных групп. Посчитаем долю пользователей, совершивших это событие. Проверим, будет ли отличие между группами статистически достоверным. Проделаем то же самое для всех других событий. Поймем, можно ли сказать, что разбиение на группы работает корректно.
аналогично поступим с группой с изменённым шрифтом. Сравним результаты с каждой из контрольных групп в отдельности по каждому событию. Сравним результаты с объединённой контрольной группой. Сделаем выводы из эксперимента.
определим какой уровень значимости мы выбрали при проверке статистических гипотез выше. Посчитаем, сколько проверок статистических гипотез сделали.
# определим сколько пользователей в каждой группе
data_new.groupby('exp_id')['device_id_hash'].nunique()
exp_id 246 2484 247 2513 248 2537 Name: device_id_hash, dtype: int64
Контрольной группе 246 у нас 2484 пользователя, что на 29 меньше, чем в 247-контрольной группе (2413 человек), а вот в экспериментальную группу 248 попало 2537 пользователей, что на 53 человека (2,1%) больше, чем в группе 246 и на 24 покупателя (менее 1%) больше, чем в 247 группе. Данные отличия совершенно незначительмы и мы можем считать, что тестируемые группы набраны однородно.
Проверим, находят ли статистические критерии разницу между выборками всех наших групп.
Н0 = между долями пользователей групп, совершивших событие, нет значимой разницы
Н1 = между долями пользователей групп, совершивших событие, есть значимая разница
Пороговое значение альфа возьмем равное 0,05
Cамым популярным событием у нас является MainScreenAppear. Мы уже увидели число пользователей, совершивших это событие в контрольных группах. Проверим, будет ли отличие между группами статистически достоверным. Ниже мы проводим расчет по всем нашим событиям и отдельно опишем самое популярное.
def function (group1, group2):
ex_group1 = data_new.query('exp_id == @group1').groupby('event_name').agg({'device_id_hash':'nunique'}).reset_index().sort_values(by = 'device_id_hash')
ex_group2 = data_new.query('exp_id == @group2').groupby('event_name').agg({'device_id_hash':'nunique'}).reset_index().sort_values(by = 'device_id_hash')
leads_group1 = data_new.query('exp_id == @group1')['device_id_hash'].nunique()
leads_group2 = data_new.query('exp_id == @group2')['device_id_hash'].nunique()
alpha = 0.05
for i in ex_group1.index:
purchases_group1 = ex_group1.device_id_hash[i]
purchases_group2 = ex_group2.device_id_hash[i]
p1 = purchases_group1/leads_group1
p2 = purchases_group2/leads_group2
p_combined = (purchases_group1 + purchases_group2) / (leads_group1 + leads_group2)
difference = p1 - p2
z_value = difference / mth.sqrt(
p_combined * (1 - p_combined) * (1 /leads_group1 + 1 /leads_group2)
)
distr = st.norm(0, 1)
p_value = (1 - distr.cdf(abs(z_value))) * 2
print('')
print('Событие', i, )
print('')
print('p-значение: ', p_value)
if p_value < alpha:
print('Отвергаем нулевую гипотезу: между долями пользователей групп нет значимой разницы')
else:
print(
'Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница'
)
print("Проверка гипотез между долями пользователей групп 246 и 247")
function (246, 247)
Проверка гипотез между долями пользователей групп 246 и 247 Событие 4 p-значение: 0.9376996189257114 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница Событие 3 p-значение: 0.11456679313141849 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница Событие 0 p-значение: 0.22883372237997213 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница Событие 2 p-значение: 0.2480954578522181 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница Событие 1 p-значение: 0.7570597232046099 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница
Стаститические критерии не находят разницы между контрольными группами 246 и 247 по всем событиям.
При этом мы критерий значимости взяли в размере 0.05, без поправки Бонферонни. У нас произошло 5 проверок, alpha следовало бы брать равным 0.05/5 = 0.01. Но как мы видим уже резултаты p-value далеки от критического значения и мы можем оставить расчеты с alpha = 0.05. Ложно положительного результата мы не получим.
print("Проверка гипотез между долями пользователей групп 246 и 248")
function (246, 248)
Проверка гипотез между долями пользователей групп 246 и 248 Событие 4 p-значение: 0.8264294010087645 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница Событие 3 p-значение: 0.2122553275697796 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница Событие 0 p-значение: 0.07842923237520116 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница Событие 2 p-значение: 0.20836205402738917 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница Событие 1 p-значение: 0.2949721933554552 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница
Стаститические критерии не находят разницы между контрольными группами 246 и 248 по всем событиям.
print("Проверка гипотез между долями пользователей групп 247 и 248")
function (247, 248)
Проверка гипотез между долями пользователей групп 247 и 248 Событие 4 p-значение: 0.765323922474501 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница Событие 3 p-значение: 0.7373415053803964 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница Событие 0 p-значение: 0.5786197879539783 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница Событие 2 p-значение: 0.9197817830592261 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница Событие 1 p-значение: 0.4587053616621515 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница
Стаститические критерии не находят разницы между контрольными группами 247 и 248 по всем событиям.
# для проверки гипотез между долями пользователей объединенных контрольных групп 246 и 247 и экспериментальной группой 248 снесем изменения в функцию
def funct (group1, group2, group3):
ex_group1 = data_new.query('exp_id == @group1').groupby('event_name').agg({'device_id_hash':'nunique'}).reset_index().sort_values(by = 'device_id_hash')+data_new.query('exp_id == @group3').groupby('event_name').agg({'device_id_hash':'nunique'}).reset_index().sort_values(by = 'device_id_hash')
ex_group2 = data_new.query('exp_id == @group2').groupby('event_name').agg({'device_id_hash':'nunique'}).reset_index().sort_values(by = 'device_id_hash')
leads_group1 = data_new.query('exp_id == @group1')['device_id_hash'].nunique()+data_new.query('exp_id == @group3')['device_id_hash'].nunique()
leads_group2 = data_new.query('exp_id == @group2')['device_id_hash'].nunique()
alpha = 0.05
for i in ex_group1.index:
purchases_group1 = ex_group1.device_id_hash[i]
purchases_group2 = ex_group2.device_id_hash[i]
p1 = purchases_group1/leads_group1
p2 = purchases_group2/leads_group2
p_combined = (purchases_group1 + purchases_group2) / (leads_group1 + leads_group2)
difference = p1 - p2
z_value = difference / mth.sqrt(
p_combined * (1 - p_combined) * (1 /leads_group1 + 1 /leads_group2)
)
distr = st.norm(0, 1)
p_value = (1 - distr.cdf(abs(z_value))) * 2
print('')
print('Событие', i, )
print('')
print('p-значение: ', p_value)
if p_value < alpha:
print('Отвергаем нулевую гипотезу: между долями пользователей групп нет значимой разницы')
else:
print(
'Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница'
)
print("Проверка гипотез между долями пользователей объединенных контрольных групп 247 и 248 и экспериментальной группой 248")
funct (247, 248, 246)
Проверка гипотез между долями пользователей объединенных контрольных групп 247 и 248 и экспериментальной группой 248 Событие 4 p-значение: 0.764862472531507 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница Событие 3 p-значение: 0.6004294282308704 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница Событие 0 p-значение: 0.18175875284404386 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница Событие 2 p-значение: 0.43425549655188256 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница Событие 1 p-значение: 0.29424526837179577 Не получилось отвергнуть нулевую гипотезу, нет оснований считать, что между долями пользователей групп есть значимая разница
Все расчеты верны, различия между группами не обнаружены 👍
Но стоит печатать название события, а не только его номер
Стаститические критерии не находят разницы между объединенными контрольными группами 246 и 247 и экспериментальной функцией 248 по всем событиям.
По результатм исследования, получаем, что все определенные нами нулевые гипотезы достоверны.
Статистические критерии не видят разницы между всеми нашими группами, и контрольными и экспериметальной.
Мы провели 20 проверку статстических гипотез и все они подтвержадют, что изменение шрифта ни как не повлияет на покупательскую способность. Дизайнеры не внесли ни каких положительных изменений в финансовый результат компании.
При этом мы в нашем исследовании использовали критический уровень статистической значимости равный 0,05. Так как мы проводили множественное тестирование, с каждой проверкой гипотезы мы могли получить результат со значимым отличием долей, что было бы ложным. Следовало бы применить поправку Бонферрони и в проверках гипотез использовать критерий значимости равный 0,05/20=0,0025. Но как мы уже видим результаты тестирования, наши полученные p-value все были далеки от критического значения. Перепроверять результаты тестирования с учетом поправки не станем.
Нам была поставлена задача разобраться, как ведут себя пользователи нашего мобильного приложения.
Мы изучили и привели в соответстие предоставленные нам данные: переименовали столбцы, изменили формат даты, определили период исследования и убрали из данных "лишнее".
Исследуемый период был определен, как с 01.08.2019 по 07.08.2019.
Убедились, что три исследуемые группы пользователей эксперимента у нас в данных присутствуют. При этом контрольные группы 246 и 247 отличаются друг от друга на 29 пользователей, что составляет 1% , а экспериментальная 248 отличается от 246 на 53 пользователя (2%), а от 247 группы на 24 пользователя (0,955%).
Получили 5 видов событий: "главный экран" - 117328 событий, "экран с предложениями" - 46333 событий, "экран с корзиной" - 42303 событий, экран "оплата прошла успешно" - 33918 событий и экран "руководство" - 1005 событий.
Выяснили, что в долевом отношении события выстроились в следующем порядке:
7419 пользователей, 0.98, перешли на главный экран
4593 пользователя, 0.61, попали на экран с предложениями с продуктами питания
3734 пользователя, 0.50, отметились на экране с корзиной товаров
3539 пользователей, 0.47, увидили экран с ответом об успешно поведенной оплате
840 пользователей, 0.11, посетили сраницу с руководством (обучением, консультацией).
Из данных мы увидели, что последний этап, с наименьшей долей пользователей, можно не включать в дальнейшее исследование и исключили его из дальнейшего исследования.
Порядок действий пользователей выглядит следующим образом: перешли на главный экран --> перешли на преложения товаров --> перешли в корзину --> успешно оплатили покупки.
Определили, что из всего количества посетителей, только 0,619 доля переходит на страницу товаров. Это больше третьей части от всех посетелителей мы терям. Вот в следующих этапах такой большой доли потерь нет. В корзину переходят 0.81 доля посетителей со страницы товаров, а из корзины переходят к успешной оплате практически все посетители, 0.95 доля от всех, совершивших выбор товара.
В итоге мы определили, что половина, а точнее 0.477 часть покупателей, попавшая на главную страницу доводит дело до успешной покупки.
Далее мы определили, что в контрольной группе 246 у нас 2484 пользователя, что на 29 меньше, чем в 247-контрольной группе (2413 человек), а вот в экспериментальную группу 248 попало 2537 пользователей, что на 53 человека (2,1%) больше, чем в группе 246 и на 24 покупателя (менее 1%) больше, чем в 247 группе. Данные отличия совершенно незначительмы и мы можем считать, что тестируемые группы набраны однородно.
У нас были 2 контрольные группы для А/А-эксперимента, чтобы проверить корректность всех механизмов и расчётов. Мы проверили, находят ли статистические критерии разницу между выборками 246 и 247. Определили гипотезы: Н0 = между долями пользователей групп 246 и 247, совершивших событие, нет значимой разницы и Н1 = между долями пользователей групп 246 и 247, совершивших событие, есть значимая разница. Приняли пороговое значение альфа равное 0,05. Получили p-значение: 0.2622344959255778, что свидетельствует о том, что нулевую гипотезу мы не отвергли, нет оснований считать, что между долями пользователей групп 246 и 247, совершивших событие, есть значимая разница.
Так же мы провели исследование и определили гипотезы и убедились, что стаститические критерии не находят разницы между контрольными группами 246 и 247 по всем имеющимся у нас событиям.
Убедились, что разбиение на группы работает корректно.
Аналогично поступили с группой с изменённым шрифтом. Сравнили результаты с каждой из контрольных групп в отдельности по каждому событию и с объединенно контрольной группой. Так же подтвердились нулевые гипотезы и не найдена была разница.
В ходе проверки 21 гипотезы мы получали один и тот же результат - нет различий между контрольными группами, между экспериментальной и каждой контрольной, между экспериментальной и обощенной контрольной.
Изменение шрифта во всем приложении ничего не изменило в поведении покупателей.
Тестирование можно не продолжать. Премию дизайнерам не обещать.